﻿/*
   Copyright 2013 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using Spring.Aop;
using Spring.Aop.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Text;
using System.Threading.Tasks;

namespace Flower.Services.Clients
{
    /// <summary>
    /// Creates reusable WCF client proxies.
    /// </summary>
    /// <typeparam name="TSvc">Service interface.</typeparam>
    public class WcfClientFactory<TSvc> where TSvc: class
    {
        private readonly IList<IAdvisor> _advisors;
        private readonly IList<IEndpointBehavior> _behaviors;

        public WcfClientFactory()
        {
            _advisors = null;
            _behaviors = null;
        }

        public WcfClientFactory(IList<IAdvisor> advisors, IList<IEndpointBehavior> behaviors)
        {
            _advisors = advisors;
            _behaviors = behaviors;
        }

        public TSvc Create(string endpointConfigurationName)
        {
            if (endpointConfigurationName == null) throw new ArgumentNullException("endpointConfigurationName");

            return CreateProxy(() => new WcfClient<TSvc>(endpointConfigurationName));
        }

        public TSvc Create(string endpointConfigurationName, string remoteAddress)
        {
            if (endpointConfigurationName == null) throw new ArgumentNullException("endpointConfigurationName");
            if (remoteAddress == null) throw new ArgumentNullException("remoteAddress");

            return CreateProxy(() => new WcfClient<TSvc>(endpointConfigurationName, remoteAddress));
        }

        public TSvc Create(string endpointConfigurationName, EndpointAddress remoteAddress)
        {
            if (endpointConfigurationName == null) throw new ArgumentNullException("endpointConfigurationName");
            if (remoteAddress == null) throw new ArgumentNullException("remoteAddress");

            return CreateProxy(() => new WcfClient<TSvc>(endpointConfigurationName, remoteAddress));
        }

        public TSvc Create(Binding binding, string remoteAddress)
        {
            if (binding == null) throw new ArgumentNullException("binding");
            if (remoteAddress == null) throw new ArgumentNullException("remoteAddress");

            return CreateProxy(() => new WcfClient<TSvc>(binding, new EndpointAddress(remoteAddress)));
        }

        public TSvc Create(Binding binding, EndpointAddress remoteAddress)
        {
            if (binding == null) throw new ArgumentNullException("binding");
            if (remoteAddress == null) throw new ArgumentNullException("remoteAddress");

            return CreateProxy(() => new WcfClient<TSvc>(binding, remoteAddress));
        }

        public TSvc CreateBalancingClient(IList<TSvc> clients)
        {
            if (clients == null) throw new ArgumentNullException("clients");
            if (clients.Count == 0) throw new ArgumentException("Clients list must not be empty.");

            return (TSvc)Wrap(new ProxyFactory(typeof(TSvc), new BalancingInterceptor<TSvc>(clients)).GetProxy());
        }

        public TSvc CreateFailoverClient(IList<TSvc> clients)
        {
            if (clients == null) throw new ArgumentNullException("clients");
            if (clients.Count == 0) throw new ArgumentException("Clients list must not be empty.");

            return (TSvc)Wrap(new ProxyFactory(typeof(TSvc), new FailoverInterceptor<TSvc>(clients)).GetProxy());
        }

        public TSvc CreateBroadcastClient(IList<TSvc> clients)
        {
            if (clients == null) throw new ArgumentNullException("clients");
            if (clients.Count == 0) throw new ArgumentException("Clients list must not be empty.");

            return (TSvc)Wrap(new ProxyFactory(typeof(TSvc), new BroadcastInterceptor<TSvc>(clients)).GetProxy());
        }

        internal object CreateBalancingClientDynamic(IList clients)
        {
            if (clients == null) throw new ArgumentNullException("clients");
            if (clients.Count == 0) throw new ArgumentException("Clients list must not be empty.");

            return Wrap(new ProxyFactory(typeof(TSvc), new BalancingInterceptor<TSvc>(clients)).GetProxy());
        }

        internal object CreateFailoverClientDynamic(IList clients)
        {
            if (clients == null) throw new ArgumentNullException("clients");
            if (clients.Count == 0) throw new ArgumentException("Clients list must not be empty.");

            return Wrap(new ProxyFactory(typeof(TSvc), new FailoverInterceptor<TSvc>(clients)).GetProxy());
        }

        internal object CreateBroadcastClientDynamic(IList clients)
        {
            if (clients == null) throw new ArgumentNullException("clients");
            if (clients.Count == 0) throw new ArgumentException("Clients list must not be empty.");

            return Wrap(new ProxyFactory(typeof(TSvc), new BroadcastInterceptor<TSvc>(clients)).GetProxy());
        }

        private TSvc CreateProxy(Func<WcfClient<TSvc>> createClient)
        {
            return (TSvc)Wrap(new ProxyFactory(typeof(TSvc), new WcfClientInterceptor<TSvc>(createClient, _behaviors)).GetProxy());
        }

        private object Wrap(object service)
        {
            if (_advisors != null)
            {
                var proxyFactory = new ProxyFactory(service);

                foreach (IAdvisor adv in _advisors)
                {
                    proxyFactory.AddAdvisor(adv);
                }

                return proxyFactory.GetProxy();
            }
            else
            {
                return service;
            }
        }
    }

    /// <summary>
    /// Creates reusable WCF client proxies.
    /// </summary>
    public class WcfClientFactory
    {
        /// <summary>
        /// The contract implemented by the service.
        /// </summary>
        public string Contract { get; set; }

        /// <summary>
        /// The custom advisors to be applied to the client.
        /// </summary>
        public IList Advisors { get; set; }

        /// <summary>
        /// The endpoint behaviors to be applied to the client.
        /// </summary>
        public IList Behaviors { get; set; }

        private readonly Lazy<dynamic> _factory;

        public WcfClientFactory()
        {
            _factory = new Lazy<dynamic>(CreateFactory);
        }

        private dynamic CreateFactory()
        {
            if (Contract == null)
            {
                throw new TypeLoadException("Contract is not specified.");
            }

            var contractType = Type.GetType(Contract);
            if (contractType == null)
            {
                throw new TypeLoadException(string.Format("Failed to load type {0}. Type not found.", Contract));
            }

            var factoryType = typeof(WcfClientFactory<>).MakeGenericType(contractType);

            var factoryTypeConstructor = 
                factoryType.GetConstructor(new Type[] { typeof(IList<IAdvisor>), typeof(IList<IEndpointBehavior>) });
            return factoryTypeConstructor.Invoke
            (
                new object[] 
                { 
                    (Advisors != null) ? Advisors.Cast<IAdvisor>().ToArray() : null,
                    (Behaviors != null) ? Behaviors.Cast<IEndpointBehavior>().ToArray() : null
                }
            );
        }
        
        public object Create(string endpointConfigurationName)
        {
            return _factory.Value.Create(endpointConfigurationName);
        }

        public object Create(string endpointConfigurationName, string remoteAddress)
        {
            return _factory.Value.Create(endpointConfigurationName, remoteAddress);
        }

        public object Create(string endpointConfigurationName, EndpointAddress remoteAddress)
        {
            return _factory.Value.Create(endpointConfigurationName, remoteAddress);
        }
        
        public object Create(Binding binding, string remoteAddress)
        {
            return _factory.Value.Create(binding, remoteAddress);
        }
        
        public object Create(Binding binding, EndpointAddress remoteAddress)
        {
            return _factory.Value.Create(binding, remoteAddress);
        }

        public object CreateBalancingClient(IList clients)
        {
            return _factory.Value.CreateBalancingClientDynamic(clients);
        }

        public object CreateFailoverClient(IList clients)
        {
            return _factory.Value.CreateFailoverClientDynamic(clients);
        }

        public object CreateBroadcastClient(IList clients)
        {
            return _factory.Value.CreateBroadcastClientDynamic(clients);
        }
    }
}
